Java 26-Day Course - Day 14: Exception Handling

Day 14: Exception Handling

An exception is an unexpected situation that occurs during program execution. Exception handling prevents the program from abruptly terminating and allows appropriate recovery logic to execute. Java treats exceptions as objects and provides a rich exception hierarchy.

try-catch-finally Basics

The basic structure for catching and handling exceptions.

public class ExceptionBasic {
    public static void main(String[] args) {
        // Basic try-catch
        try {
            int result = 10 / 0;
            System.out.println("Result: " + result); // Not executed
        } catch (ArithmeticException e) {
            System.out.println("Cannot divide by zero: " + e.getMessage());
        }

        // Handling multiple exceptions
        try {
            String text = null;
            // text.length(); // NullPointerException

            int[] arr = {1, 2, 3};
            System.out.println(arr[5]); // ArrayIndexOutOfBoundsException
        } catch (NullPointerException e) {
            System.out.println("Null reference error: " + e.getMessage());
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Array index out of bounds: " + e.getMessage());
        } catch (Exception e) {
            System.out.println("Other error: " + e.getMessage());
        } finally {
            // Always executes regardless of whether an exception occurred
            System.out.println("Cleanup executed (finally)");
        }

        // Multi-catch (Java 7+)
        try {
            String numStr = "abc";
            int num = Integer.parseInt(numStr);
        } catch (IllegalArgumentException e) {
            System.out.println("Conversion error: " + e.getMessage());
        }

        System.out.println("Program continues running");
    }
}

try-with-resources (Automatic Resource Release)

Automatically closes resources that implement AutoCloseable.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class TryWithResources {
    // Custom resource
    static class DatabaseConnection implements AutoCloseable {
        String name;

        DatabaseConnection(String name) {
            this.name = name;
            System.out.println(name + " connection opened");
        }

        void query(String sql) {
            System.out.println(name + " executing query: " + sql);
        }

        @Override
        public void close() {
            System.out.println(name + " connection closed");
        }
    }

    public static void main(String[] args) {
        // try-with-resources: close() is called automatically
        try (DatabaseConnection db = new DatabaseConnection("MySQL")) {
            db.query("SELECT * FROM users");
            // db.close() is called automatically
        }

        // Managing multiple resources
        try (
            DatabaseConnection db1 = new DatabaseConnection("Primary");
            DatabaseConnection db2 = new DatabaseConnection("Secondary")
        ) {
            db1.query("INSERT INTO logs VALUES (...)");
            db2.query("SELECT * FROM cache");
        }
        // Closed in reverse order: db2 first, then db1

        System.out.println("All resources cleaned up");
    }
}

Checked vs Unchecked Exceptions

Understanding the Java exception hierarchy and the difference between two types of exceptions.

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;

public class CheckedUnchecked {
    // Checked exception: compiler enforces handling
    // IOException, SQLException, FileNotFoundException, etc.
    static String readFile(String path) throws FileNotFoundException {
        File file = new File(path);
        if (!file.exists()) {
            throw new FileNotFoundException("File not found: " + path);
        }
        return "File content";
    }

    // Unchecked exception (RuntimeException): handling not enforced
    // NullPointerException, IllegalArgumentException, etc.
    static int divide(int a, int b) {
        if (b == 0) {
            throw new IllegalArgumentException("Divisor cannot be zero.");
        }
        return a / b;
    }

    static void validateAge(int age) {
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("Invalid age: " + age);
        }
        System.out.println("Valid age: " + age);
    }

    public static void main(String[] args) {
        // Checked: must handle with try-catch or throws
        try {
            String content = readFile("nonexistent.txt");
        } catch (FileNotFoundException e) {
            System.out.println("Checked exception: " + e.getMessage());
        }

        // Unchecked: no compile error even without handling (but recommended)
        try {
            int result = divide(10, 0);
        } catch (IllegalArgumentException e) {
            System.out.println("Unchecked exception: " + e.getMessage());
        }

        validateAge(25);
        // validateAge(-5); // IllegalArgumentException thrown
    }
}

Custom Exceptions

Create domain-specific exceptions for meaningful error handling.

// Custom checked exception
class InsufficientBalanceException extends Exception {
    private final long currentBalance;
    private final long requestedAmount;

    InsufficientBalanceException(long currentBalance, long requestedAmount) {
        super(String.format("Insufficient balance: current %,d, requested %,d",
                            currentBalance, requestedAmount));
        this.currentBalance = currentBalance;
        this.requestedAmount = requestedAmount;
    }

    public long getShortfall() {
        return requestedAmount - currentBalance;
    }
}

// Custom unchecked exception
class InvalidAccountException extends RuntimeException {
    InvalidAccountException(String accountNumber) {
        super("Invalid account number: " + accountNumber);
    }
}

class BankService {
    private long balance;

    BankService(long initialBalance) {
        this.balance = initialBalance;
    }

    void withdraw(long amount) throws InsufficientBalanceException {
        if (amount <= 0) {
            throw new IllegalArgumentException("Withdrawal amount must be positive.");
        }
        if (amount > balance) {
            throw new InsufficientBalanceException(balance, amount);
        }
        balance -= amount;
        System.out.println(String.format("%,d withdrawn. Balance: %,d", amount, balance));
    }

    long getBalance() {
        return balance;
    }
}

public class CustomExceptionExample {
    public static void main(String[] args) {
        BankService bank = new BankService(100000);

        try {
            bank.withdraw(50000);   // Success
            bank.withdraw(80000);   // Insufficient balance
        } catch (InsufficientBalanceException e) {
            System.out.println(e.getMessage());
            System.out.println("Shortfall: " + String.format("%,d", e.getShortfall()));
        }

        try {
            bank.withdraw(-1000);   // Invalid amount
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }
    }
}

Today’s Exercises

  1. Input Validator: Create a Validator class that validates username, email, and password. Throw a custom ValidationException for each validation failure, and collect multiple validation errors to report them all at once.

  2. File Processor: Write a program that uses try-with-resources to open a resource (custom Connection class), and verify that the resource is safely closed even if an exception occurs during processing.

  3. Exception Chaining: Implement a 3-level exception chain where DataAccessException wraps SQLException, and ServiceException wraps DataAccessException. Trace the root cause using getCause().

Was this article helpful?